#include <stdio.h>
#include <string.h>
#include "MEM.h"
#include "DBG.h"
#include "wuji.h"

static void
check_gc(WJ_Interpreter *inter)
{
#if 0
    wj_garbage_collect(inter);
#endif
    if (inter->heap.current_heap_size > inter->heap.current_threshold) {
        /* fprintf(stderr, "garbage collecting..."); */
        wj_garbage_collect(inter);
        /* fprintf(stderr, "done.\n"); */

        inter->heap.current_threshold
            = inter->heap.current_heap_size + HEAP_THRESHOLD_SIZE;
    }
}

static WJ_Object *
alloc_object(WJ_Interpreter *inter, ObjectType type)
{
    WJ_Object *ret;

    check_gc(inter);
    ret = MEM_malloc(sizeof(WJ_Object));
    inter->heap.current_heap_size += sizeof(WJ_Object);
    ret->type = type;
    ret->marked = WJ_FALSE;
    ret->prev = NULL;
    ret->next = inter->heap.header;
    inter->heap.header = ret;
    if (ret->next) {
        ret->next->prev = ret;
    }

    return ret;
}

static void
add_ref_in_native_method(WJ_LocalEnvironment *env, WJ_Object *obj)
{
    RefInNativeFunc *new_ref;

    new_ref = MEM_malloc(sizeof(RefInNativeFunc));
    new_ref->object = obj;
    new_ref->next = env->ref_in_native_method;
    env->ref_in_native_method = new_ref;
}

WJ_Object *
wj_literal_to_wj_string_i(WJ_Interpreter *inter, char *str)
{
	WJ_Object *ret;

	ret = alloc_object(inter, STRING_OBJECT);
	ret->u.string.string = str;
	ret->u.string.is_literal = WJ_TRUE;

	return ret;
}

WJ_Object *
WJ_literal_to_wj_string(WJ_Interpreter *inter, WJ_LocalEnvironment *env, char *str)
{
    WJ_Object *ret;

	ret = wj_literal_to_wj_string_i(inter, str);
	add_ref_in_native_method(env, ret);

    return ret;
}

WJ_Object *
wj_create_wuji_string_i(WJ_Interpreter *inter, char *str)
{
    WJ_Object *ret;

    ret = alloc_object(inter, STRING_OBJECT);
    ret->u.string.string = str;
    inter->heap.current_heap_size += strlen(str) + 1;
    ret->u.string.is_literal = WJ_FALSE;

    return ret;
}

WJ_Object *
WJ_create_wuji_string(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
                          char *str)
{
    WJ_Object *ret;

    ret = wj_create_wuji_string_i(inter, str);
    add_ref_in_native_method(env, ret);

    return ret;
}

WJ_Object *
wj_string_substr_i(WJ_Interpreter *inter, WJ_LocalEnvironment *env, 
				WJ_Object *str, int from, int len, int line_number)
{
	int org_len = strlen(str->u.string.string);
	char *new_str;

	if (from < 0 || from >= org_len) {
		wj_runtime_error(line_number, STRING_POS_OUT_OF_BOUNDS_ERR,
					INT_MESSAGE_ARGUMENT, "len", org_len,
					INT_MESSAGE_ARGUMENT, "pos", from,
					MESSAGE_ARGUMENT_END);
	}
	if (len < 0 || from + len > org_len) {
		wj_runtime_error(line_number, STRING_SUBSTR_LEN_ERR,
					INT_MESSAGE_ARGUMENT, "len", len,
					MESSAGE_ARGUMENT_END);
	}
	new_str = MEM_malloc(len + 1);
	strncpy(new_str, str->u.string.string + from, len);
	/* strcpy(new_str, str->u.string.string + from); */
	new_str[len] = '\0';

	return wj_create_wuji_string_i(inter, new_str);
}

WJ_Object *
WJ_string_substr(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
			WJ_Object *str, int from, int len, int line_number)
{
	WJ_Object *ret;

	ret = wj_string_substr_i(inter, env, str, from, len, line_number);
	add_ref_in_native_method(env, ret);

	return ret;
}


WJ_Object *
wj_create_array_i(WJ_Interpreter *inter, int size)
{
    WJ_Object *ret;

    ret = alloc_object(inter, ARRAY_OBJECT);
    ret->u.array.size = size;
    ret->u.array.alloc_size = size;
    ret->u.array.array = MEM_malloc(sizeof(WJ_Value) * size);
    inter->heap.current_heap_size += sizeof(WJ_Value) * size;

    return ret;
}

WJ_Object *
WJ_create_array(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
                 int size)
{
    WJ_Object *ret;

    ret = wj_create_array_i(inter, size);
    add_ref_in_native_method(env, ret);

    return ret;
}

void
WJ_array_resize(WJ_Interpreter *inter, WJ_Object *obj, int new_size)
{
    int new_alloc_size;
    WJ_Boolean need_realloc;
    int i;

    check_gc(inter);
    
    if (new_size > obj->u.array.alloc_size) {
        new_alloc_size = obj->u.array.alloc_size * 2;
        if (new_alloc_size < new_size) {
            new_alloc_size = new_size + ARRAY_ALLOC_SIZE;
        } else if (new_alloc_size - obj->u.array.alloc_size
                   > ARRAY_ALLOC_SIZE) {
            new_alloc_size = obj->u.array.alloc_size + ARRAY_ALLOC_SIZE;
        }
        need_realloc = WJ_TRUE;
    } else if (obj->u.array.alloc_size - new_size > ARRAY_ALLOC_SIZE) {
        new_alloc_size = new_size;
        need_realloc = WJ_TRUE;
    } else {
        need_realloc = WJ_FALSE;
    }
    if (need_realloc) {
        check_gc(inter);
        obj->u.array.array = MEM_realloc(obj->u.array.array,
                                         new_alloc_size * sizeof(WJ_Value));
        inter->heap.current_heap_size
            += (new_alloc_size - obj->u.array.alloc_size) * sizeof(WJ_Value);
        obj->u.array.alloc_size = new_alloc_size;
    }
    for (i = obj->u.array.size; i < new_size; i++) {
        obj->u.array.array[i].type = WJ_NULL_VALUE;
    }
    obj->u.array.size = new_size;
}

void
WJ_array_add(WJ_Interpreter *inter, WJ_Object *obj, WJ_Value *v)
{
	DBG_assert(obj->type == ARRAY_OBJECT, ("bad type..%d\n", obj->type));

	WJ_array_resize(inter, obj, obj->u.array.size + 1);
	obj->u.array.array[obj->u.array.size - 1] = *v;
}

void
WJ_array_insert(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
				WJ_Object *obj, int pos,
				WJ_Value *new_value, int line_number)
{
	int i;
	DBG_assert(obj->type == ARRAY_OBJECT, ("bad type.. %d\n", obj->type));

	if (pos < 0 || pos > obj->u.array.size) {
		wj_runtime_error(line_number, 
				ARRAY_INDEX_OUT_OF_BOUNDS_ERR,
				INT_MESSAGE_ARGUMENT, "size", obj->u.array.size,
				INT_MESSAGE_ARGUMENT, "index", pos,
				MESSAGE_ARGUMENT_END);
	}
	WJ_array_resize(inter, obj, obj->u.array.size + 1);
	for (i = obj->u.array.size - 2; i >= pos; i--) {
		obj->u.array.array[i+1] = obj->u.array.array[i];
	}
	obj->u.array.array[pos] = *new_value;
}

void 
WJ_array_remove(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
				WJ_Object *obj, int pos, int line_number)
{
	int i;

	DBG_assert(obj->type == ARRAY_OBJECT, ("bad type.. %d\n", obj->type));
	if (pos < 0 || pos > obj->u.array.size - 1) {
		wj_runtime_error(line_number, 
				ARRAY_INDEX_OUT_OF_BOUNDS_ERR,
				INT_MESSAGE_ARGUMENT, "size", obj->u.array.size,
				INT_MESSAGE_ARGUMENT, "index", pos,
				MESSAGE_ARGUMENT_END);
	}
	for (i = pos + 1; i < obj->u.array.size; i++) {
		obj->u.array.array[i - 1] = obj->u.array.array[i];
	}
	WJ_array_resize(inter, obj, obj->u.array.size - 1);
}


WJ_Object *
wj_create_assoc_i(WJ_Interpreter *inter)
{
	WJ_Object *ret;

	ret = alloc_object(inter, ASSOC_OBJECT);
	ret->u.assoc.member_count = 0;
	ret->u.assoc.member = NULL;

	return ret;
}

WJ_Object *
WJ_create_assoc(WJ_Interpreter *inter, WJ_LocalEnvironment *env)
{
	WJ_Object *ret;

	ret = wj_create_assoc_i(inter);
	add_ref_in_native_method(env, ret);

	return ret;
}

WJ_Value *
WJ_add_assoc_member(WJ_Interpreter *inter, WJ_Object *assoc,
					char *name, WJ_Value *value, WJ_Boolean is_final)
{
	AssocMember *member_p;

	check_gc(inter);
	member_p = MEM_realloc(assoc->u.assoc.member,
							sizeof(AssocMember) * 
							(assoc->u.assoc.member_count + 1));

	member_p[assoc->u.assoc.member_count].name = name;
	member_p[assoc->u.assoc.member_count].value = *value;
	member_p[assoc->u.assoc.member_count].is_final = is_final;

	assoc->u.assoc.member = member_p;
	assoc->u.assoc.member_count++;
	inter->heap.current_heap_size += sizeof(AssocMember);

	return &member_p[assoc->u.assoc.member_count - 1].value;
}

void 
WJ_add_assoc_member2(WJ_Interpreter *inter, WJ_Object *assoc,
					char *name, WJ_Value *value)
{
	int i;

	if (assoc->u.assoc.member_count > 0) {
		for (i = 0; i < assoc->u.assoc.member_count; i++) {
			if (!strcmp(assoc->u.assoc.member[i].name, name)) {
				break;
			}
		}
		if (i < assoc->u.assoc.member_count) {
			assoc->u.assoc.member[i].value = *value;
			return;
		}
	}
	WJ_add_assoc_member(inter, assoc, name, value, WJ_FALSE);
}

WJ_Value *
WJ_search_assoc_member(WJ_Object *assoc, char *member_name)
{
	WJ_Value *ret = NULL;
	int i;

	if (assoc->u.assoc.member_count == 0)
		return NULL;

	for (i = 0; i < assoc->u.assoc.member_count; i++) {
		if (!strcmp(assoc->u.assoc.member[i].name, member_name)) {
			ret = &assoc->u.assoc.member[i].value;
			break;
		}
	}

	return ret;
}

WJ_Value *
WJ_search_assoc_member_w(WJ_Object *assoc, char *member_name,
						WJ_Boolean *is_final)
{
	WJ_Value *ret = NULL;
	int i;

	if (assoc->u.assoc.member_count == 0)
		return NULL;

	for (i = 0; i < assoc->u.assoc.member_count; i++) {
		if (!strcmp(assoc->u.assoc.member[i].name, member_name)) {
			ret = &assoc->u.assoc.member[i].value;
			*is_final = assoc->u.assoc.member[i].is_final;
			break;
		}
	}
	return ret;
}

WJ_Object *
wj_create_scope_chain(WJ_Interpreter *inter)
{
	WJ_Object *ret;

	ret = alloc_object(inter, SCOPE_CHAIN_OBJECT);
	ret->u.scope_chain.frame = NULL;
	ret->u.scope_chain.next = NULL;

	return ret;
}

WJ_Object *
wj_create_native_pointer_i(WJ_Interpreter *inter, void *pointer,
							WJ_NativePointerInfo *info)
{
	WJ_Object *ret;

	ret = alloc_object(inter, NATIVE_POINTER_OBJECT);
	ret->u.native_pointer.pointer = pointer;
	ret->u.native_pointer.info = info;

	return ret;
}

WJ_Object *
WJ_create_native_pointer(WJ_Interpreter *inter, WJ_LocalEnvironment *env,
						void *pointer, WJ_NativePointerInfo *info)
{
	WJ_Object *ret;

	ret = wj_create_native_pointer_i(inter, pointer, info);
	add_ref_in_native_method(env, ret);

	return ret;
}

WJ_NativePointerInfo *
WJ_get_native_pointer_type(WJ_Object *native_pointer)
{
	return native_pointer->u.native_pointer.info;
}

WJ_Boolean
WJ_check_native_pointer_type(WJ_Object *native_pointer,
							WJ_NativePointerInfo *info)
{
	return native_pointer->u.native_pointer.info == info;
}

/* static int */
/* count_stack_trace_depth(WJ_LocalEnvironment *top) */
/* { */
	/* WJ_LocalEnvironment *pos; */

	/* int count = 0; */

	/* for (pos = top; pos; pos = pos->next) { */
		/* count ++; */
	/* } */
	/* return count + 1; */
/* } */

/* static WJ_Object * */
/* create_stack_trace_line(WJ_Interpreter *inter, WJ_LocalEnvironment *env, */
						/* char *func_name, int line_number) */
/* { */
	/* char	*wc_func_name; */
	/* WJ_Object *new_line; */
	/* WJ_Value new_line_value; */
	/* WJ_Value value; */
	/* int stack_count = 0; */

	/* new_line = wj_create_assoc_i(inter); */
	/* new_line_value.type = WJ_ASSOC_VALUE; */
	/* new_line_value.u.object = new_line; */
	/* WJ_push_value(inter, &new_line_value); */
	/* stack_count++; */

	/* wc_func_name = MEM_malloc(strlen(func_name) + 1); */
	/* strcpy(wc_func_name, func_name, strlen(func_name)); */
	/* wc_func_name[strlen(func_name) + 1] = '\0'; */

	/* DBG_assert(wc_func_name != NULL, ("wc_func_name is null.\n")); */

	/* value.type = WJ_STRING_VALUE; */
	/* value.u.object = wj_create_wuji_string_i(inter, wc_func_name); */
	/* WJ_push_value(inter, &value); */
	/* stack_count++; */

	/* WJ_add_assoc_member(inter, new_line, EXCEPTION_MEMBER_FUNCTION_NAME, */
						/* &value, WJ_TRUE); */

	/* value.type = WJ_INT_VALUE; */
	/* value.u.int_value = line_number; */
	/* WJ_add_assoc_member(inter, new_line, EXCEPTION_MEMBER_LINE_NUMBER, */
						/* &value, WJ_TRUE); */

	/* WJ_shrink_stack(inter, stack_count); */

	/* return new_line; */
/* } */

/* static WJ_Value */
/* print_stack_trace(WJ_Interpreter *inter, WJ_LocalEnvironment *env, */
				/* int arg_count, WJ_Value *args) */
/* { */
	/* WJ_Value *message; */
	/* WJ_Value *stack_trace; */
	/* WJ_Value ret; */
	/* int		i; */

	/* message = WJ_search_local_variable(env, EXCEPTION_MEMBER_MESSAGE); */
	/* if (message == NULL) { */
		/* wj_runtime_error(__LINE__, EXCEPTION_HAS_NO_MESSAGE_ERR, */
						/* MESSAGE_ARGUMENT_END); */
	/* } */
	/* if (message->type != WJ_STRING_VALUE) { */
		/* wj_runtime_error(__LINE__, EXCEPTION_MESSAGE_IS_NOT_STRING_ERR, */
						/* STRING_MESSAGE_ARGUMENT, "type", */
						/* WJ_get_type_name(message->type), */
						/* MESSAGE_ARGUMENT_END); */
	/* } */
/* } */

static void gc_mark_value(WJ_Value *v);

static void
gc_mark(WJ_Object *obj)
{
	if (obj == NULL)
		return;

    if (obj->marked)
        return;

    obj->marked = WJ_TRUE;

    if (obj->type == ARRAY_OBJECT) {
        int i;
        for (i = 0; i < obj->u.array.size; i++) {
			gc_mark_value(&obj->u.array.array[i]);
        }
    } else if (obj->type == ASSOC_OBJECT) {
		int i;
		for (i = 0; i < obj->u.assoc.member_count; i++) {
			gc_mark_value(&obj->u.assoc.member[i].value);
		}
	} else if (obj->type == SCOPE_CHAIN_OBJECT) {
		gc_mark(obj->u.scope_chain.frame);
		gc_mark(obj->u.scope_chain.next);
	}
}

static void
gc_reset_mark(WJ_Object *obj)
{
    obj->marked = WJ_FALSE;
}

static void
gc_mark_value(WJ_Value *v)
{
	if (wj_is_object_value(v->type)) {
		gc_mark(v->u.object);
	} else if (v->type == WJ_CLOSURE_VALUE) {
		if (v->u.closure.environment) {
			gc_mark(v->u.closure.environment);
		}
	} else if (v->type == WJ_FAKE_METHOD_VALUE) {
		gc_mark(v->u.fake_method.object);
	}
}

static void
gc_mark_ref_in_native_method(WJ_LocalEnvironment *env)
{
    RefInNativeFunc *ref;

    for (ref = env->ref_in_native_method; ref; ref = ref->next) {
        gc_mark(ref->object);
    }
}

static void
gc_mark_objects(WJ_Interpreter *inter)
{
    WJ_Object *obj;
    Variable *v;
    WJ_LocalEnvironment *lv;
    int i;

    for (obj = inter->heap.header; obj; obj = obj->next) {
        gc_reset_mark(obj);
    }
    
    for (v = inter->variable; v; v = v->next) {
		gc_mark_value(&v->value);
    }
    
    for (lv = inter->top_environment; lv; lv = lv->next) {
		gc_mark(lv->variable);
		gc_mark_ref_in_native_method(lv);
    }

    for (i = 0; i < inter->stack.stack_pointer; i++) {
		gc_mark_value(&inter->stack.stack[i]);
    }
	gc_mark_value(&inter->current_exception);
}

static void
gc_dispose_object(WJ_Interpreter *inter, WJ_Object *obj)
{
    switch (obj->type) {
    case ARRAY_OBJECT:
        inter->heap.current_heap_size
            -= sizeof(WJ_Value) * obj->u.array.alloc_size;
        MEM_free(obj->u.array.array);
        break;
    case STRING_OBJECT:
        if (!obj->u.string.is_literal) {
            inter->heap.current_heap_size -= strlen(obj->u.string.string) + 1;
            MEM_free(obj->u.string.string);
        }
        break;
	case ASSOC_OBJECT:
		inter->heap.current_heap_size
			-= sizeof(AssocMember) * obj->u.assoc.member_count;
		MEM_free(obj->u.assoc.member);
		break;
	case SCOPE_CHAIN_OBJECT:
		break;
	case NATIVE_POINTER_OBJECT:
		if (obj->u.native_pointer.info->finalizer) {
			obj->u.native_pointer.info->finalizer(inter, obj);
		}
		break;
    case OBJECT_TYPE_COUNT_PLUS_1:
    default:
        DBG_assert(0, ("bad type..%d\n", obj->type));
    }
    inter->heap.current_heap_size -= sizeof(WJ_Object);
    MEM_free(obj);
}

static void
gc_sweep_objects(WJ_Interpreter *inter)
{
    WJ_Object *obj;
    WJ_Object *tmp;

    for (obj = inter->heap.header; obj; ) {
        if (!obj->marked) {
            if (obj->prev) {
                obj->prev->next = obj->next;
            } else {
                inter->heap.header = obj->next;
            }
            if (obj->next) {
                obj->next->prev = obj->prev;
            }
            tmp = obj->next;
            gc_dispose_object(inter, obj);
            obj = tmp;
        } else {
            obj = obj->next;
        }
    }
}

void
wj_garbage_collect(WJ_Interpreter *inter)
{
    gc_mark_objects(inter);
    gc_sweep_objects(inter);
}
